/*
 * Copyright (C) 2008 Wim Crols <twimpiex@gnx.be>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 3
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package be.gnx.event.consumer.receiver.model;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.spi.LoggingEvent;

public class EventsFileCache implements Events {

  private File loggingEventsFile;

  private FileOutputStream loggingEventsFileOutputStream;

  private int numberOfLoggingEvents = 0;

  private static final int defaultMaxCachedLoggingEvents = 100;

  private int maxCachedLoggingEvents;

  private Hashtable<Integer, Event> cachedLoggingEvents;

  private List<Integer> cachedLoggingEventIndexes;

  private static final int defaultTwoTimesPositionCache = 1000;

  private int twoTimesPositionCache;

  private int loggingEventPositionTwoPowerGapSize;

  private List<Long> loggingEventPosition;

  public EventsFileCache() {
    this(defaultMaxCachedLoggingEvents, defaultTwoTimesPositionCache);
  }

  public EventsFileCache(int maxCachedLoggingEvents, int twoTimesPositionCache) {
    this.maxCachedLoggingEvents = maxCachedLoggingEvents;
    this.twoTimesPositionCache = twoTimesPositionCache;
    try {
      loggingEventsFile = File.createTempFile("logging4eclipse", "log");
      loggingEventsFile.deleteOnExit();
      loggingEventsFileOutputStream = new FileOutputStream(loggingEventsFile);
    } catch (IOException ioException) {
      throw new RuntimeException(ioException);
    }
    cachedLoggingEvents = new Hashtable<Integer, Event>(maxCachedLoggingEvents);
    cachedLoggingEventIndexes = new ArrayList<Integer>(maxCachedLoggingEvents);
    loggingEventPosition = new ArrayList<Long>(2 * twoTimesPositionCache);
    loggingEventPositionTwoPowerGapSize = 0;
  }

  public boolean add(Event loggingEvent) {
    if (cachedLoggingEventIndexes.size() >= maxCachedLoggingEvents) {
      cachedLoggingEvents.remove(cachedLoggingEventIndexes.get(0));
      cachedLoggingEventIndexes.remove(0);
    }
    cachedLoggingEvents.put(numberOfLoggingEvents, loggingEvent);
    cachedLoggingEventIndexes.add(numberOfLoggingEvents);
    try {
      ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
      // ObjectOutputStream objectOutputStream = new
      // ObjectOutputStream(byteArrayOutputStream);
      // objectOutputStream.writeObject(loggingEvent);
      // byteArrayOutputStream.write(loggingEvent.getEventClass().toString().getBytes());
      loggingEvent.serialize(byteArrayOutputStream);
      byte[] loggingEventbytes = byteArrayOutputStream.toByteArray();
      if ((loggingEventPosition.size() >= (2 * twoTimesPositionCache))
          && ((numberOfLoggingEvents & ((1 << loggingEventPositionTwoPowerGapSize) - 1)) == 0)) {
        List<Long> newLoggingEventPosition = new ArrayList<Long>(
            2 * twoTimesPositionCache);
        for (int index = 0; index < loggingEventPosition.size(); index = index + 2) {
          newLoggingEventPosition.add(loggingEventPosition.get(index));
        }
        loggingEventPosition = newLoggingEventPosition;
        loggingEventPositionTwoPowerGapSize++;
      }
      if ((loggingEventPositionTwoPowerGapSize == 0)
          || ((numberOfLoggingEvents & ((1 << loggingEventPositionTwoPowerGapSize) - 1)) == 0)) {
        loggingEventPosition.add(loggingEventsFileOutputStream.getChannel()
            .position());
      }
      loggingEventsFileOutputStream.write(loggingEventbytes);
      loggingEventsFileOutputStream.flush();
    } catch (IOException ioException) {
      throw new RuntimeException(ioException);
    }
    numberOfLoggingEvents++;
    return true;
  }

  public void clear() {
    try {
      loggingEventsFileOutputStream.getChannel().truncate(0);
    } catch (IOException ioException) {
      throw new RuntimeException(ioException);
    }
    cachedLoggingEvents.clear();
    cachedLoggingEventIndexes.clear();
    loggingEventPosition.clear();
    numberOfLoggingEvents = 0;
  }

  public Event get(int index) {
    if (cachedLoggingEventIndexes.contains(index)) {
      cachedLoggingEventIndexes
          .remove(cachedLoggingEventIndexes.indexOf(index));
      cachedLoggingEventIndexes.add(index);
      return cachedLoggingEvents.get(index);
    }
    if (cachedLoggingEventIndexes.size() >= maxCachedLoggingEvents) {
      cachedLoggingEvents.remove(cachedLoggingEventIndexes.get(0));
      cachedLoggingEventIndexes.remove(0);
    }
    Event loggingEvent = read(index);
    cachedLoggingEvents.put(index, loggingEvent);
    cachedLoggingEventIndexes.add(index);
    return loggingEvent;
  }

  public int size() {
    return numberOfLoggingEvents;
  }

  public Event read(int index) {
    int currentIndex = (index >> loggingEventPositionTwoPowerGapSize) << loggingEventPositionTwoPowerGapSize;
    long currentPosition = loggingEventPosition
        .get(index >> loggingEventPositionTwoPowerGapSize);
    Event loggingEvent = null;
    try {
      FileInputStream loggingEventsFileInputStream = new FileInputStream(
          loggingEventsFile);
      loggingEventsFileInputStream.getChannel().position(currentPosition);
      while (currentIndex <= index) {
        loggingEvent = new Log4jEvent((LoggingEvent) new ObjectInputStream(
            loggingEventsFileInputStream).readObject());
        currentIndex++;
      }
      loggingEventsFileInputStream.close();
    } catch (IOException ioException) {
      throw new RuntimeException(ioException);
    } catch (ClassNotFoundException classNotFoundException) {
      throw new RuntimeException(classNotFoundException);
    }

    return loggingEvent;
  }

  public Iterator<Event> iterator() {
    // TODO Auto-generated method stub
    return null;
  };

  public boolean addAll(Collection<? extends Event> c) {
    // TODO Auto-generated method stub
    return false;
  }

  public boolean contains(Object o) {
    // TODO Auto-generated method stub
    return false;
  }

  public boolean containsAll(Collection<?> c) {
    // TODO Auto-generated method stub
    return false;
  }

  public boolean remove(Object o) {
    // TODO Auto-generated method stub
    return false;
  }

  public boolean removeAll(Collection<?> c) {
    // TODO Auto-generated method stub
    return false;
  }

  public boolean retainAll(Collection<?> c) {
    // TODO Auto-generated method stub
    return false;
  }

  public boolean isEmpty() {
    // TODO Auto-generated method stub
    return false;
  }

  public Object[] toArray() {
    // TODO Auto-generated method stub
    return null;
  }

  public <T> T[] toArray(T[] a) {
    // TODO Auto-generated method stub
    return null;
  }

}
